package com.hqd.ch03.v36.web.servlet;


import com.hqd.ch03.v36.context.stereotype.Controller;
import com.hqd.ch03.v36.web.context.request.ServletWebRequest;
import com.hqd.ch03.v36.web.http.HttpMethod;
import com.hqd.ch03.v36.web.servlet.handler.RequestMappingHandlerMapping;
import com.hqd.ch03.v36.web.servlet.handler.SimpleServletHandlerAdapter;
import com.hqd.ch03.v36.web.servlet.handler.SimpleUrlHandlerMapping;
import com.hqd.ch03.v36.web.servlet.mvc.ServletWrappingController;
import com.hqd.ch03.v36.web.servlet.mvc.SimpleControllerHandlerAdapter;
import com.hqd.ch03.v36.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import com.hqd.ch03.v36.web.servlet.view.InternalResourceViewResolver;
import org.reflections.Reflections;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;

public class DispatcherServlet extends HttpServlet {
    private static final String PREFIX = "/views/";
    private static final String SUFFIX = ".jsp";
    private static final String BASE_PACKAGE = "com.hqd.ch03.test.web.v36";
    List<HandlerMapping> handlerMappings = new ArrayList<>();
    private List<HandlerAdapter> handlerAdapters = new ArrayList<>();
    private List<ViewResolver> viewResolvers;

    @Override
    public void init(ServletConfig config) throws ServletException {
        initStrategies();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doDispatch(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doDispatch(req, resp);
    }

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws IOException {
        HandlerExecutionChain mappedHandler = null;
        Exception ex = null;
        ModelAndView mv = null;
        try {
            mappedHandler = getHandler(request);
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
            String method = request.getMethod();
            boolean isGet = HttpMethod.GET.matches(method);
            if (isGet || HttpMethod.HEAD.matches(method)) {
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                /**
                 * 资源没改变，直接返回
                 */
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }
            if (mappedHandler == null) {
                response.sendError(404);
            } else {
                mappedHandler.applyPreHandle(request, response);
                mv = ha.handle(request, response, mappedHandler.getHandler());
                mappedHandler.applyPostHandle(request, response, mv);
                processDispatchResult(request, response, mappedHandler, mv, ex);
            }
        } catch (Exception e) {
            response.sendError(500);
            e.printStackTrace();
            ex = e;
        } finally {
            if (mappedHandler != null) {
                mappedHandler.triggerAfterCompletion(request, response, ex);
            }
        }
    }

    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
                                       HandlerExecutionChain mappedHandler, ModelAndView mv,
                                       Exception exception) throws Exception {
        if (exception != null) {
            response.sendError(500);
            return;
        }
        if (mv != null && !mv.wasCleared()) {
            /**
             * 渲染
             */
            render(mv, request, response);
        }

        if (mappedHandler != null) {
            mappedHandler.triggerAfterCompletion(request, response, null);
        }
    }

    protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
        View view;
        String viewName = mv.getViewName();
        if (viewName != null) {
            view = resolveViewName(viewName, mv.getModelInternal(), null, request);
            if (view == null) {
                throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
                        "' in servlet with name '" + getServletName() + "'");
            }
        } else {
            view = mv.getView();
            if (view == null) {
                throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
                        "View object in servlet with name '" + getServletName() + "'");
            }
        }
        try {
            if (mv.getStatus() != null) {
                request.setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, mv.getStatus());
                response.setStatus(mv.getStatus().value());
            }
            view.render(mv.getModelInternal(), request, response);
        } catch (Exception ex) {
            throw ex;
        }
    }

    protected View resolveViewName(String viewName, Map<String, Object> model,
                                   Locale locale, HttpServletRequest request) throws Exception {
        if (this.viewResolvers != null) {
            /**
             * 使用事务定位器进行定位
             */
            for (ViewResolver viewResolver : this.viewResolvers) {
                View view = viewResolver.resolveViewName(viewName, locale);
                if (view != null) {
                    return view;
                }
            }
        }
        return null;
    }

    protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
        if (this.handlerAdapters != null) {
            for (HandlerAdapter adapter : this.handlerAdapters) {
                if (adapter.supports(handler)) {
                    return adapter;
                }
            }
        }
        throw new ServletException("No adapter for handler [" + handler +
                "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
    }


    protected void initStrategies() {
        initHandlerMappings();
        initHandlerAdapters();
        initViewResolvers();
    }

    private void initViewResolvers() {
        this.viewResolvers = new ArrayList<>();
        this.viewResolvers.add(new InternalResourceViewResolver(PREFIX, SUFFIX));
    }


    private void initHandlerAdapters() {
        this.handlerAdapters.add(new SimpleControllerHandlerAdapter());
        this.handlerAdapters.add(new SimpleServletHandlerAdapter());
        RequestMappingHandlerAdapter rmha = new RequestMappingHandlerAdapter();
        try {
            rmha.afterPropertiesSet();
        } catch (Exception e) {
            throw new RuntimeException("初始化异常:", e);
        }
        this.handlerAdapters.add(rmha);
    }

    private void initHandlerMappings() {
        RequestMappingHandlerMapping rmhm = new RequestMappingHandlerMapping();
        SimpleUrlHandlerMapping shm = new SimpleUrlHandlerMapping();
        Reflections reflections = new Reflections(BASE_PACKAGE);
        // 获取指定包下所有类
        Set<Class<?>> classes = reflections.getTypesAnnotatedWith(Controller.class);
        Set<Class<? extends HandlerInterceptor>> interceptors = reflections.getSubTypesOf(HandlerInterceptor.class);
        Set<Class<? extends HttpServlet>> cs = reflections.getSubTypesOf(HttpServlet.class);
        for (Class<? extends Servlet> clazz : cs) {
            try {
                ServletWrappingController swc = new ServletWrappingController(clazz);
                swc.afterPropertiesSet();
                //这里默认使用类名，实际上应该由用户配置
                shm.registerHandler("/" + clazz.getSimpleName(), swc);
            } catch (Exception e) {
                throw new RuntimeException("class [" + clazz.getName() + "] create is error，because：" + e.getMessage());
            }
        }
        /**
         * 注册控制器
         */
        for (Class<?> clazz : classes) {
            try {
                rmhm.init(clazz.newInstance());
            } catch (Exception e) {
                throw new RuntimeException("class [" + clazz.getName() + "] create is error，because：" + e.getMessage());
            }
        }
        /**
         * 注册拦截器
         */
        for (Class<? extends HandlerInterceptor> clazz : interceptors) {
            try {
                rmhm.setInterceptors(clazz.newInstance());
            } catch (Exception e) {
                throw new RuntimeException("class [" + clazz.getName() + "] create is error，because：" + e.getMessage());
            }
        }
        rmhm.initInterceptors();

        this.handlerMappings.add(rmhm);
        this.handlerMappings.add(shm);
    }

    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        if (this.handlerMappings != null) {
            for (HandlerMapping mapping : this.handlerMappings) {
                HandlerExecutionChain handler = mapping.getHandler(request);
                if (handler != null) {
                    return handler;
                }
            }
        }
        return null;
    }
}
